// 版权所有2011 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// AMD64特定的硬件辅助CRC32算法。请参见crc32.go，了解每个特定于体系结构的文件
// 实现的接口的描述。

package crc32

import (
	"internal/cpu"
	"unsafe"
)

// 此文件包含调用SSE 4.2版Castagnoli 
// 和IEEE CRC的代码。

// castagnoliSSE42在crc32_amd64.s中定义，并使用SSE 4.2 crc32 
// 指令。
// go:noescape 
func castagnoliSSE42(crc uint32, p []byte) uint32

// castagnoliSSE42Triple在crc32_amd64.s中定义，并使用SSE 4.2 crc32 
// 指令。
// go:noescape 
func castagnoliSSE42Triple(
	crcA, crcB, crcC uint32,
	a, b, c []byte,
	rounds uint32,
) (retA uint32, retB uint32, retC uint32)

// ieeeCLMUL在crc_amd64.s中定义，并使用PCLMULQDQ 
// 指令以及SSE 4.1。
// go:noescape 
func ieeeCLMUL(crc uint32, p []byte) uint32

const castagnoliK1 = 168
const castagnoliK2 = 1344

type sse42Table [4]Table

var castagnoliSSE42TableK1 *sse42Table
var castagnoliSSE42TableK2 *sse42Table

func archAvailableCastagnoli() bool {
	return cpu.X86.HasSSE42
}

func archInitCastagnoli() {
	if !cpu.X86.HasSSE42 {
		panic("arch-specific Castagnoli not available")
	}
	castagnoliSSE42TableK1 = new(sse42Table)
	castagnoliSSE42TableK2 = new(sse42Table)
	// 请参见UpdateStagnoli中的说明。
	// t[0][i]=CRC（i000，O）
	// t[1][i]=CRC（0i00，O）
	// t[2][i]=CRC（00i0，O）
	// t[3][i]=CRC（000i，O）
	// 其中O是K个零的序列。
	var tmp [castagnoliK2]byte
	for b := 0; b < 4; b++ {
		for i := 0; i < 256; i++ {
			val := uint32(i) << uint32(b*8)
			castagnoliSSE42TableK1[b][i] = castagnoliSSE42(val, tmp[:castagnoliK1])
			castagnoliSSE42TableK2[b][i] = castagnoliSSE42(val, tmp[:])
		}
	}
}

// castagnoliShift使用给定的初始crc值计算K1或K2零的CRC32-C（取决于给定的
// 表）。这对应于updateCastagnoli中描述中的
// CRC（CRC，O）。
func castagnoliShift(table *sse42Table, crc uint32) uint32 {
	return table[3][crc>>24] ^
		table[2][(crc>>16)&0xFF] ^
		table[1][(crc>>8)&0xFF] ^
		table[0][crc&0xFF]
}

func archUpdateCastagnoli(crc uint32, p []byte) uint32 {
	if !cpu.X86.HasSSE42 {
		panic("not available")
	}

	// 此方法受Intel白皮书中算法的启发：
	// “使用CRC32指令对iSCSI多项式进行快速CRC计算”
	// 使用相同的策略将缓冲区拆分为三，但
	// 组合计算不同；完整的推导解释如下
	// 所示。
	// 
	// --基本思想——
	// 
	// CRC32指令（在SSE4.2中提供）一次可以处理8个字节。在最近的英特尔体系结构中，指令需要3个周期；
	// 但是，如果指令之间互不依赖，处理器最多可以通过管道传输三条指令。
	// 
	// 这大致意味着我们可以在大约相同的
	// 时间内处理三个缓冲区。
	// 
	// 然后将缓冲区分成三部分，分别对三部分进行CRC运算
	// 然后合并结果。
	// 
	// 合并结果需要预先计算表，因此我们必须选择固定缓冲区长度进行优化。长度越长，速度越快；但是
	// 只有长度超过此长度的缓冲区才会使用优化。我们选择两个截止值并计算两个表：一个是512:168*3=504一个是111
	// /初始非反转CRC I）。此函数具有以下属性：
	// （a）CRC（I，AB）=CRC（CRC（I，a），B）
	// （B）CRC（I，a xor B）=CRC（I，a）xor CRC（0，B）
	// 
	// 假设我们要计算CRC（I，ABC）其中A、B、C是三个
	// K字节的序列，其中K是一个固定常数。设O为K零
	// 字节的序列。
	// 
	// CRC（I，ABC）=CRC（I，ABO xor C）
	// 
	// Castagnolisse42三重函数可以高效地计算CRC（I，A），CRC（0，B），
	// 和CRC（0，C）。我们只需要找到一种快速计算
	// CRC（uvwx，O）的方法给定一个4字节的初始值uvwx。我们可以预先计算这些
	// 值；因为我们不能有一个32位的表，我们把它分成四个
	// 8位的表：
	// 
	// CRC（uvwx，O）=CRC（u000，O）xor 
	// CRC（00w0，O）xor 
	// CRC（000x，O）
	// 
	// 我们可以为所有8位
	// 值。

	crc = ^crc

	// 如果缓冲区足够长，可以使用优化，则处理前几个
	// 字节以将缓冲区与8字节边界对齐（如有必要）。
	if len(p) >= castagnoliK1*3 {
		delta := int(uintptr(unsafe.Pointer(&p[0])) & 7)
		if delta != 0 {
			delta = 8 - delta
			crc = castagnoliSSE42(crc, p[:delta])
			p = p[delta:]
		}
	}

	// 一次处理3*K2。
	for len(p) >= castagnoliK2*3 {
		// 计算CRC（I，A），CRC（0，B）和CRC（0，C）。
		crcA, crcB, crcC := castagnoliSSE42Triple(
			crc, 0, 0,
			p, p[castagnoliK2:], p[castagnoliK2*2:],
			castagnoliK2/24)

		// CRC（I，AB）=CRC（I，A），O）xor CRC（0，B）
		crcAB := castagnoliShift(castagnoliSSE42TableK2, crcA) ^ crcB
		// CRC（I，ABC）=CRC（I，AB），O）xor CRC（0，C）
		crc = castagnoliShift(castagnoliSSE42TableK2, crcAB) ^ crcC
		p = p[castagnoliK2*3:]
	}

	// 一次处理3*K1。
	for len(p) >= castagnoliK1*3 {
		// 计算CRC（I，A），CRC（0，B）和CRC（0，C）。
		crcA, crcB, crcC := castagnoliSSE42Triple(
			crc, 0, 0,
			p, p[castagnoliK1:], p[castagnoliK1*2:],
			castagnoliK1/24)

		// CRC（I，AB）=CRC（CRC（I，A），O）xor CRC（0，B）
		crcAB := castagnoliShift(castagnoliSSE42TableK1, crcA) ^ crcB
		// CRC（I，ABC）=CRC（CRC（I，AB），O）xor CRC（0，C）
		crc = castagnoliShift(castagnoliSSE42TableK1, crcAB) ^ crcC
		p = p[castagnoliK1*3:]
	}

	// 剩下的部分使用简单的实现。
	crc = castagnoliSSE42(crc, p)
	return ^crc
}

func archAvailableIEEE() bool {
	return cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41
}

var archIeeeTable8 *slicing8Table

func archInitIEEE() {
	if !cpu.X86.HasPCLMULQDQ || !cpu.X86.HasSSE41 {
		panic("not available")
	}
	// 对于小型缓冲区，我们仍然使用8倍切片。
	archIeeeTable8 = slicingMakeTable(IEEE)
}

func archUpdateIEEE(crc uint32, p []byte) uint32 {
	if !cpu.X86.HasPCLMULQDQ || !cpu.X86.HasSSE41 {
		panic("not available")
	}

	if len(p) >= 64 {
		left := len(p) & 15
		do := len(p) - left
		crc = ^ieeeCLMUL(^crc, p[:do])
		p = p[do:]
	}
	if len(p) == 0 {
		return crc
	}
	return slicingUpdate(crc, archIeeeTable8, p)
}
